package com.ccteam.admin.utils

import android.content.Context
import android.net.Uri
import androidx.core.content.ContextCompat
import cn.hutool.core.util.IdUtil
import com.arthenica.ffmpegkit.*
import com.ccteam.model.music.BITRATE_HQ
import com.ccteam.model.music.BITRATE_NQ
import com.ccteam.model.music.BITRATE_SQ
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.max
import kotlin.math.min

/**
 * @author Xiaoc
 * @since 2021/4/23
 *
 * FFmpeg相关工具集
 * 使用的是 FfmpegKit 集成的Ffmpeg库
 */
@Singleton
class FfmpegUtils @Inject constructor(
    @ApplicationContext private val context: Context
    ){

    private val bitrateBuilder = StringBuilder()
    private val streamMapBuilder = StringBuilder()

    private val OTHER_INFO_LOG_TAG = 0x213L

    private val BITRATE_NQ_NUMBER = 128
    private val BITRATE_HQ_NUMBER = 320
    private val BITRATE_SQ_NUMBER = 1024

    sealed class Flag{

        /**
         * 代表只有标准的音质
         */
        object Low: Flag()

        /**
         * 代表有标准的音质和高清音质
         */
        object Medium: Flag()

        /**
         * 代表有标准的音质和高清音质和无损音质
         */
        object High: Flag()
    }


    /**
     * 通过媒体文件Uri实现音频切割功能
     * @param fileUri 文件Uri
     * @param config 一些基本配置（例如音频比特率等重要内容）
     * @param sessionCallback 执行结果回调
     * @param logCallback 日志内容回调
     */
    suspend fun audioSegmentByUri(fileUri: Uri,
                                  config: Config,
                                  sessionCallback: (Boolean,String,List<String>,List<File>) -> Unit,
                                  logCallback: (Long,Level,String) ->Unit){
        withContext(Dispatchers.Default){
            bitrateBuilder.setLength(0)
            streamMapBuilder.setLength(0)

            val externalCacheDirs = ContextCompat.getExternalCacheDirs(context)

            if(externalCacheDirs.isNullOrEmpty()){
                logCallback(OTHER_INFO_LOG_TAG,Level.AV_LOG_ERROR,"\n没有外置存储器，请检查")
                return@withContext
            }

            // 分割前清掉之前的缓存数据
            val cachePathFile = externalCacheDirs[0]
            logCallback(OTHER_INFO_LOG_TAG,Level.AV_LOG_ERROR,"\n准备清空缓存目录")
            cachePathFile.deleteRecursively()

            // 清除缓存数据后，我们重新新建缓存文件夹进行音频分割
            val newExternalCacheDirs = ContextCompat.getExternalCacheDirs(context)

            if(newExternalCacheDirs.isNullOrEmpty()){
                logCallback(OTHER_INFO_LOG_TAG,Level.AV_LOG_ERROR,"\n没有外置存储器，请检查")
                return@withContext
            }
            val newCachePathFile = newExternalCacheDirs[0]
            val cachePath = newCachePathFile.path

            if(cachePath.isNullOrEmpty()){
                logCallback(OTHER_INFO_LOG_TAG,Level.AV_LOG_ERROR,"\n缓存路径为空，请检查")
                return@withContext
            }
            logCallback(OTHER_INFO_LOG_TAG,Level.AV_LOG_INFO,"\ncachePath：$cachePath")

            // 解析当前歌曲Bitrate，以拼接需要分割的不同内容
            val bitrateList = parseBitrateLevel(config.audioBitrate)
            logCallback(OTHER_INFO_LOG_TAG,Level.AV_LOG_INFO,"\n需要生成的Bitrate列表：$bitrateList")

            // 生成输出文件前缀
            val outPutNamePrefix = IdUtil.simpleUUID()

            bitrateList.forEachIndexed { index,data ->
                bitrateBuilder.append(
                    "-b:a:${index} ${data.first} -maxrate:a:${index} ${data.first} -map 0:a "
                )
                // 如果是SQ无损则直接按照eac3编解码器进行解析，防止bitrate不够的问题
                if(data.second == BITRATE_SQ){
                    bitrateBuilder.append("-c:a:${index} eac3")
                }
                streamMapBuilder.append(
                    "a:${index},name:${data.second} "
                )
            }
            val masterM3u8FileName = "${outPutNamePrefix}.m3u8"

            val audioPath = FFmpegKitConfig.getSafParameterForRead(context, fileUri)
            val command = "-threads 2 -thread_type slice -i \"${audioPath}\" $bitrateBuilder -f hls -var_stream_map \"$streamMapBuilder\" " +
                    "-master_pl_name \"${masterM3u8FileName}\" -hls_time ${config.segmentTime} -hls_list_size 0 " +
                    "-hls_segment_filename \"${cachePath}/${outPutNamePrefix}_%v_%03d.ts\" \"${cachePath}/${outPutNamePrefix}_%v.m3u8\" "

            logCallback(OTHER_INFO_LOG_TAG,Level.AV_LOG_INFO,"\n$command")
            FFmpegKit.executeAsync(command,{
                val success = ReturnCode.isSuccess(it.returnCode)
                sessionCallback(success,masterM3u8FileName,bitrateList.map { pair ->
                    pair.second
                },newCachePathFile.listFiles()?.toList() ?: emptyList())
            }, {
                logCallback(it.sessionId,it.level,it.message)
            },{})
        }
    }

    fun cancel(){
        FFmpegKit.cancel()
    }

    /**
     * 根据比特率来解析需要分割的内容
     *
     * 如果比特率小于NQ，则代表只需分割NQ音质
     * 如果比特率大于NQ，则代表需要分割NQ音质和HQ音质
     * 如果比特率大于HQ或SQ，则代表需要分割NQ、HQ、SQ音质
     *
     * 注，最高SQ音质有限制，最高位1020比特率
     */
    private fun parseBitrateLevel(bitrate: Int): List<Pair<String,String>>{

        val flag = when {
            bitrate <= BITRATE_NQ_NUMBER -> {
                Flag.Low
            }
            bitrate in (BITRATE_NQ_NUMBER + 1)..BITRATE_HQ_NUMBER -> {
                Flag.Medium
            }
            else -> {
                Flag.High
            }
        }

        val bitrateList = mutableListOf<Pair<String,String>>()

        when(flag){
            is Flag.Low ->{
                val maxBitrate = max(min(BITRATE_NQ_NUMBER,bitrate),1)
                bitrateList.add(Pair("${maxBitrate}K", BITRATE_NQ))
            }
            is Flag.Medium ->{
                val maxBitrate = min(BITRATE_HQ_NUMBER,bitrate)
                bitrateList.add(Pair("${BITRATE_NQ_NUMBER}K",BITRATE_NQ))
                bitrateList.add(Pair("${maxBitrate}K", BITRATE_HQ))
            }
            is Flag.High ->{
                // 如果音频的比特率 >= 900Kbps 由于FFmpeg目前的BUG，会导致put_bits太小报错
                // 所以如果大于等于900则手动置为1024.1K比特率防止出错
                // 该BUG已被最新版FFmpeg4.4修复
//                val maxBitrate = if(bitrate >= 900){
//                    BITRATE_SQ_NUMBER
//                } else {
//                    bitrate.toFloat()
//                }
                val maxBitrate = min(BITRATE_SQ_NUMBER,bitrate)
                bitrateList.add(Pair("${BITRATE_NQ_NUMBER}K",BITRATE_NQ))
                bitrateList.add(Pair("${BITRATE_HQ_NUMBER}K",BITRATE_HQ))
                bitrateList.add(Pair("${maxBitrate}K", BITRATE_SQ))
            }
        }

        return bitrateList
    }

    /**
     * 配置信息
     * @param segmentTime 分割间隔时间
     */
    data class Config(
        val audioBitrate: Int,
        val segmentTime: Int = 10
    )
}